/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2008, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

//------------------------------------------------------------------------------
//         Headers
//------------------------------------------------------------------------------

#include "draw.h"
#include "font.h"
#include <stdarg.h>
#include <board.h>
#include <utility/assert.h>

#include <string.h>

/* Used by DrawTriangle */

#define SWAP(a,b) {a ^= b; b ^= a; a ^= b;}
#define DX(x1,y1,x2,y2) (((double) (x2 - x1)) / (y2 - y1 + 1))
#define H(h) ((h < BOARD_LCD_HEIGHT) ? (h) : (BOARD_LCD_HEIGHT - 1))

static void _draw_triangle_segment(
        void *pBuffer,
        double xfrom,
        double xto,
        int y,
        color_t c) {
    int x;
    int w = (xto < BOARD_LCD_WIDTH) ? ((int) xto) : (BOARD_LCD_WIDTH - 1);
    for (x = (xfrom < 0) ? (0) : ((int) xfrom); x <= w; x++)
        LCDD_DrawPixel(pBuffer, x, y, c);
}

//------------------------------------------------------------------------------
//         Global functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Fills the given LCD buffer with a particular color.
/// Only works in 24-bits packed mode for now.
/// \param pBuffer  LCD buffer to fill.
/// \param color  Fill color.
//------------------------------------------------------------------------------
void LCDD_Fill(void *pBuffer, color_t color) {
    unsigned int i;
    unsigned char tmpBuffer[12];

    // Prepare temporary buffer
    for (i = 0; i < 4; i++)
        memcpy(&(tmpBuffer[i * 3]), &color, 3);

    // Copy in LCD buffer
    i = 0;
    while (i < (BOARD_LCD_FRAMESIZE * 4)) {
        memcpy(&(((unsigned char *) pBuffer)[i]), tmpBuffer, 12);
        i += 12;
    }
}

//------------------------------------------------------------------------------
/// Sets the specified pixel to the given color.
/// !!! Only works in 24-bits packed mode for now. !!!
/// \param pBuffer  LCD buffer to draw on.
/// \param x  X-coordinate of pixel.
/// \param y  Y-coordinate of pixel.
/// \param color  Pixel color.
//------------------------------------------------------------------------------
void LCDD_DrawPixel(void *pBuffer, unsigned int x, unsigned int y, color_t c) {
    unsigned char *pTmp = &(((unsigned char *) pBuffer)[(BOARD_LCD_WIDTH - 1
            - x) * 3 * BOARD_LCD_HEIGHT + y * 3]);
    // y * 3 * BOARD_LCD_WIDTH + x * 3

    // Modify color when using RGB565
#ifdef BOARD_LCD_RGB565
    unsigned char r;
    unsigned char g;
    unsigned char b;

    r = (c >> 16) & 0xFF;
    g = (c >> 8) & 0xFF;
    b = c & 0xFF;
    r = ((r & 0x78) << 1) | ((r & 0x80) >> 5) | ((g & 0x80) >> 4);
    g = ((g & 0x7C) << 1);
    b = (b & 0xFC);
    c = (b << 16) | (g << 8) | r;
#endif

    pTmp[0] = (c >> 16) & 0xFF;
    pTmp[1] = (c >> 8) & 0xFF;
    pTmp[2] = (c >> 0) & 0xFF;
}

//------------------------------------------------------------------------------
/// Draws a horizontal line inside a LCD buffer.
/// \param pBuffer  LCD buffer to draw on.
/// \param x  X-coordinate of the origin.
/// \param y  Y-coordinate of the origin.
/// \param width  Line width.
/// \param c  Line colour.
//------------------------------------------------------------------------------
void LCDD_DrawHorizontalLine(
        void *pBuffer,
        unsigned int x,
        unsigned int y,
        unsigned int width,
        color_t c) {
    int i;
    for (i = x; i < x + width; ++i)
        LCDD_DrawPixel(pBuffer, i, y, c);
}

//------------------------------------------------------------------------------
/// Draws a veritcal line inside a LCD buffer.
/// \param pBuffer  LCD buffer to draw on.
/// \param x  X-coordinate of the origin.
/// \param y  Y-coordinate of the origin.
/// \param height  Line height.
/// \param c  Line colour.
//------------------------------------------------------------------------------
void LCDD_DrawVerticalLine(
        void *pBuffer,
        unsigned int x,
        unsigned int y,
        unsigned int height,
        color_t c) {
    int i;

    for (i = y; i < y + height; ++i)
        LCDD_DrawPixel(pBuffer, x, i, c);
}

//------------------------------------------------------------------------------
/// Draws a rectangle inside a LCD buffer, at the given coordinates.
/// \param pBuffer  LCD buffer to draw on.
/// \param x  X-coordinate of upper-left rectangle corner.
/// \param y  Y-coordinate of upper-left rectangle corner.
/// \param width  Rectangle width in pixels.
/// \param height  Rectangle height in pixels.
/// \param color  Rectangle color.
//------------------------------------------------------------------------------
void LCDD_DrawRectangle(
        void *pBuffer,
        unsigned int x,
        unsigned int y,
        unsigned int width,
        unsigned int height,
        color_t color) {
    unsigned int rx, ry;

    for (ry = 0; ry < height; ++ry)
        for (rx = 0; rx < width; ++rx)
            LCDD_DrawPixel(pBuffer, x + rx, y + ry, color);
}

//------------------------------------------------------------------------------
/// Draws a triangle inside a LCD buffer, at the given coordinates.
/// \param pBuffer  LCD buffer to draw on.
/// \param xa  X-coordinate of the first point.
/// \param ya  Y-coordinate of the first point.
/// \param xb  X-coordinate of the second point.
/// \param yb  Y-coordinate of the second point.
/// \param xc  X-coordinate of the third point.
/// \param yc  Y-coordinate of the third point.
/// \param colour  Triangle colour.
//------------------------------------------------------------------------------
void LCDD_DrawTriangle(
        void *pBuffer,
        int x0,
        int y0,
        int x1,
        int y1,
        int x2,
        int y2,
        color_t c) {
    double dx_02, dx_01, dx_12;
    double xfrom, xto;
    double *from, *to;
    int y, h;

    // sort the points vertically
    if (y1 > y2) {
        SWAP(x1, x2);
        SWAP(y1, y2);
    }
    if (y0 > y1) {
        SWAP(x0, x1);
        SWAP(y0, y1);
        if (y1 > y2) {
            SWAP(x1, x2);
            SWAP(y1, y2);
        }
    }

    dx_02 = DX(x0, y0, x2, y2);
    dx_01 = DX(x0, y0, x1, y1);
    dx_12 = DX(x1, y1, x2, y2);

    xfrom = xto = x0;
    if (dx_12 < 0) {
        from = &xfrom;
        to = &xto;
    } else {
        from = &xto;
        to = &xfrom;
    }

    y = (y0 < 0) ? (0) : (y0);
    h = H(y1);
    do {
        xto += dx_01;
        _draw_triangle_segment(pBuffer, *from, *to, y, c);
        y++;
        xfrom += dx_02;
    } while (y <= h);

    h = H(y2);
    for (; y <= h; y++, xfrom += dx_02) {
        xto += dx_12;
        _draw_triangle_segment(pBuffer, *from, *to, y, c);
    }
}

//------------------------------------------------------------------------------
/// Draws a circle inside a LCD buffer, at the given coordinates.
/// \param pBuffer  LCD buffer to draw on.
/// \param xc  X-coordinate of the center.
/// \param yc  Y-coordinate of the center.
/// \param r  Radius of the circle.
/// \param c  Circle colour.
//------------------------------------------------------------------------------
void LCDD_DrawCircle(
        void *pBuffer,
        unsigned int xc,
        unsigned int yc,
        unsigned int r,
        color_t c) {
    int x, y;
    int left = xc - r, right = xc + r;
    int up = yc - r, down = yc + r;

    for (x = 0; x <= r; ++x) {
        for (y = 0; y <= r; ++y) {
            if ((x - r) * (x - r) + (y - r) * (y - r) <= r * r) {
                int xl, xr, yu, yd;
                xl = left + x;
                xr = right - x;
                yu = up + y;
                yd = down - y;
                LCDD_DrawPixel(pBuffer, xl, yu, c);
                LCDD_DrawPixel(pBuffer, xr, yu, c);
                LCDD_DrawPixel(pBuffer, xr, yd, c);
                LCDD_DrawPixel(pBuffer, xl, yd, c);
            }
        }
    }
}

//------------------------------------------------------------------------------
/// Draws a string inside a LCD buffer, at the given coordinates. Line breaks
/// will be honored.
/// \param pBuffer  Buffer to draw on.
/// \param x  X-coordinate of string top-left corner.
/// \param y  Y-coordinate of string top-left corner.
/// \param pString  String to display.
/// \param color  String color.
//------------------------------------------------------------------------------
void LCDD_DrawString(
        void *pBuffer,
        unsigned int x,
        unsigned int y,
        const char *pString,
        color_t color) {
    unsigned xorg = x;

    while (*pString != 0) {
        if (*pString == '\n') {
            y += gFont.height + 2;
            x = xorg;
        } else {
            LCDD_DrawChar(pBuffer, x, y, *pString, color);
            x += gFont.width + 2;
        }
        pString++;
    }
}

//------------------------------------------------------------------------------
/// Draws a string inside a LCD buffer, at the given coordinates. Line breaks
/// will be honored.
/// \param pBuffer  Buffer to draw on.
/// \param x  X-coordinate of string top-left corner.
/// \param y  Y-coordinate of string top-left corner.
/// \param pString  String to display.
/// \param c  String color.
//------------------------------------------------------------------------------
void LCDD_DrawFormattedString(
    void *pBuffer,
    unsigned int x,
    unsigned int y,
    color_t c,
    const char *pFormatString,
    ...)
{
    char str[80];
    va_list ap;

    va_start(ap, pFormatString);
    vsprintf(str, pFormatString, ap);
    va_end(ap);

    LCDD_DrawString(pBuffer, x, y, str, c);
}

//------------------------------------------------------------------------------
/// Returns the width & height in pixels that a string will occupy on the screen
/// if drawn using LCDD_DrawString.
/// \param pString  String.
/// \param pWidth  Pointer for storing the string width (optional).
/// \param pHeight  Pointer for storing the string height (optional).
/// \return String width in pixels.
//------------------------------------------------------------------------------
void LCDD_GetStringSize(
        const char *pString,
        unsigned int *pWidth,
        unsigned int *pHeight) {
    unsigned int width = 0;
    unsigned int height = gFont.height;

    while (*pString != 0) {

        if (*pString == '\n')
            height += gFont.height + 2;
        else
            width += gFont.width + 2;
        pString++;
    }

    if (width > 0)
        width -= 2;

    if (pWidth)
        *pWidth = width;

    if (pHeight)
        *pHeight = height;
}
